import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 多线程下载模型
 *
 * @author bridge
 */
public class MutiThreadDownLoad {

    /**
     * 同时下载的线程数
     */
    private int threadCount;
    /**
     * 服务器请求路径
     */
    private String serverPath;
    /**
     * 本地路径
     */
    private String localPath;
    /**
     * 线程计数同步辅助
     */
    private CountDownLatch latch;


    private long FileSize;

    private long currentDispatchSize = 0;

    public MutiThreadDownLoad(int threadCount, String serverPath, CountDownLatch latch) {
        this.threadCount = threadCount;
        this.serverPath = serverPath;
        this.latch = latch;
    }

    private AtomicLong currentDownloadSize = new AtomicLong(0);

    private AtomicLong preDownloadSize = new AtomicLong(0);

    public Timer timer = new Timer();

    public void executeDownLoad() {

        try {
            URL url = new URL(serverPath);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11");
            conn.setConnectTimeout(TimeOut);
            conn.setReadTimeout(TimeOut);
            int code = conn.getResponseCode();
            if (code == 200) {
                this.localPath = getFileName(conn);

                //服务器返回的数据的长度，实际上就是文件的长度,单位是字节
                FileSize = conn.getContentLengthLong();
                conn.disconnect();
                Logger.info("文件大小为:" + FileSize / Million + " MB");
                RandomAccessFile raf = new RandomAccessFile(localPath, fileMode);
                //指定创建的文件的长度
                raf.setLength(FileSize);
                raf.close();
                //启动线程
                for (int threadId = 0; threadId < threadCount; threadId++) {
                    new DownLoadThread(threadId).start();
                }

                timer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        outputDownloadProgressInfo();
                    }
                }, 0, 1000);


            } else {
                Logger.error("连接失败: " + serverPath);
                for (int i = 0; i < threadCount; i++) {
                    latch.countDown();
                }
            }

        } catch (Exception e) {
            Logger.error("连接异常: " + serverPath);
            for (int i = 0; i < threadCount; i++) {
                latch.countDown();
            }
            e.printStackTrace();
        }

    }

    private final String fileMode = "rws";

    private final int Million = 1024 * 1024;

    // 128 KB
    final int BufferSize = 1024 * 128;

    // 128 KB
    private final long minDispatchSize = 1024 * 128;

    // 8 MB
    private final long maxDispatchSize = 1024 * 1024 * 8;

    synchronized private boolean dispatchTaskBlock(DownLoadThread thread) {
        if (currentDispatchSize == FileSize) return false;

        long leftSize = (FileSize - currentDispatchSize);
        long dispatchSize = leftSize / threadCount;
        if (dispatchSize > maxDispatchSize) dispatchSize = maxDispatchSize;
        if (dispatchSize < minDispatchSize) dispatchSize = minDispatchSize;
        if (dispatchSize > leftSize) dispatchSize = leftSize;
        thread.setStartIndex(currentDispatchSize);
        thread.setEndIndex(currentDispatchSize + dispatchSize - 1);
        currentDispatchSize += dispatchSize;
        return true;
    }

    private String getFileName(HttpURLConnection conn) {
        String urlFileNameString = conn.getURL().getFile();
        String name = urlFileNameString.substring(urlFileNameString.lastIndexOf('/') + 1);
        String contentDisposition = conn.getHeaderField("Content-Disposition");
        if (contentDisposition != null) {
            String[] headerField = contentDisposition.split(";");
            final String key = "filename=";
            for (String s : headerField) {
                if (s.indexOf(key) != -1) {
                    try {
                        name = new String(s.substring(s.indexOf(key) + key.length()).getBytes("ISO-8859-1"), "UTF-8");
                        name = name.replace("\"", "");
                    } catch (UnsupportedEncodingException e) {
                        Logger.warning("文件名解析失败");
                        e.printStackTrace();
                    }
                }
            }
        }
        return name;
    }

    private void outputDownloadProgressInfo() {
        double speed = 1.0 * (currentDownloadSize.longValue() - preDownloadSize.longValue()) / Million;
        double percentage = 100.0 * currentDownloadSize.longValue() / FileSize;
        int second = (int) ((FileSize - currentDownloadSize.longValue()) / speed / Million);
        Logger.info(String.format("下载速度%.2f MB/S, 已下载%.2f%%, 预计完成时间%d分%d秒", speed, percentage, second / 60, second % 60));
        preDownloadSize.set(currentDownloadSize.get());
    }

    /**
     * 超时时间,单位为毫秒
     */
    final int TimeOut = 30000;

    /**
     * 内部类用于实现下载
     */
    public class DownLoadThread extends Thread {
        /**
         * 线程ID
         */
        private int threadId;
        /**
         * 下载起始位置
         */
        private long startIndex;
        /**
         * 下载结束位置
         */
        private long endIndex;

        public void setStartIndex(long startIndex) {
            this.startIndex = startIndex;
        }

        public void setEndIndex(long endIndex) {
            this.endIndex = endIndex;
        }

        /**
         * 当前下载位置
         */

        public DownLoadThread(int threadId) {
            super("我是线程" + threadId);
            this.threadId = threadId;
        }

        private int inputBufferLen = 0;
        private byte[] inputBuffer = new byte[BufferSize];
        private RandomAccessFile raf = null;
        private HttpURLConnection conn = null;
        private InputStream inputStream = null;

        @Override
        public void run() {
            while (dispatchTaskBlock(this)) {
                doTask();
            }
            latch.countDown();
        }

        private void doTask() {
            Logger.debug("线程" + threadId + "领取到新任务" + startIndex + "-" + endIndex + ",任务大小为" + (endIndex - startIndex + 1));
            for (; ; ) {
                try {
                    raf = new RandomAccessFile(localPath, fileMode);
                    //随机写文件的时候从哪个位置开始写,注意需要减去缓冲区内容大小
                    raf.seek(startIndex - inputBufferLen);

                    if (startIndex > endIndex) {
                        writeToDisk();
                        break;
                    }

                    URL url = new URL(serverPath);
                    conn = (HttpURLConnection) url.openConnection();
                    conn.setRequestMethod("GET");
                    //请求服务器下载部分的文件的指定位置
                    conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
                    conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11");
                    conn.setConnectTimeout(TimeOut);
                    conn.setReadTimeout(TimeOut);
                    int code = conn.getResponseCode();
                    if (code != 206) {
                        Logger.warning("线程" + threadId + "连接失败,将要重试");
                        continue;
                    }

                    inputStream = conn.getInputStream();

                    int readLen;
                    while ((readLen = inputStream.read(inputBuffer, inputBufferLen, BufferSize - inputBufferLen)) != -1) {
                        inputBufferLen += readLen;
                        startIndex += readLen;
                        currentDownloadSize.addAndGet(readLen);
                        if (inputBufferLen == BufferSize) {
                            writeToDisk();
                        }
                    }
                } catch (Exception e) {
                    Logger.info("线程" + threadId + "发生异常,重新请求下载");
                    e.printStackTrace();
                } finally {
                    try {
                        if (inputStream != null) inputStream.close();
                        if (conn != null) conn.disconnect();
                        if (raf != null) raf.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        private void writeToDisk() throws IOException {
            raf.write(inputBuffer, 0, inputBufferLen);
            inputBufferLen = 0;
        }

    }
}
